16. 보안의 기본과 개념2 심화 - TLS


SSL/TLS 개요

SSL/TLS란?

암호화 통신을 수행하는 절차를 규정한 프로토콜. 공개키 암호화 방식을 이용하여 연결 대상의 정당성을 확인한 후 암호화 통신에 필요한 키를 생성하고, 패킷 암호화와 복호화를 실행함

역사적 발전

SSL/TLS 발전 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1990년대 중반]
SSL 1.0 (공개 안 됨)
    ↓
SSL 2.0 (Netscape)
    ↓
SSL 3.0 (1996)
    ↓

[1999년]
TLS 1.0 (SSL 3.1)
    ↓
[2006년]
TLS 1.1
    ↓
[2008년]
TLS 1.2 (현재 널리 사용)
    ↓
[2018년]
TLS 1.3 (최신 표준)

→ SSL 3.0, TLS 1.0, 1.1: 취약점 발견
→ TLS 1.2, 1.3: 권장 프로토콜

프로토콜 배경:


TLS 핸드셰이크 완전 분석

TLS 핸드셰이크란?

TLS 통신을 시작하기 전에 클라이언트와 서버가:

  1. 서로 인증하고
  2. 암호화 방식을 협상하며
  3. 안전하게 세션 키를 교환하는 과정

전체 과정 (TLS 1.2 기준)

TLS 핸드셰이크 9단계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[클라이언트]                           [서버]
     |                                    |
     |--① ClientHello----------------→|
     |                                    |
     |   전송 내용:                        |
     |   • 지원하는 TLS 버전               |
     |     (TLS 1.2, TLS 1.3 등)          |
     |   • 지원하는 암호화 알고리즘 목록     |
     |     (AES-256-GCM, RSA-2048 등)     |
     |   • 클라이언트 랜덤 (32바이트)       |
     |     "abc123..."                    |
     |   • 세션 ID (재사용 여부)           |
     |                                    |
     |←-② ServerHello------------------|
     |                                    |
     |   전송 내용:                        |
     |   • 선택한 TLS 버전                 |
     |   • 선택한 암호화 알고리즘           |
     |   • 서버 랜덤 (32바이트)            |
     |     "xyz789..."                    |
     |   • 세션 ID                        |
     |                                    |
     |←-③ Certificate------------------|
     |                                    |
     |   전송 내용:                        |
     |   • 서버 인증서 (X.509 형식)        |
     |     - 서버 도메인 정보              |
     |     - 서버 공개키                   |
     |     - CA의 전자 서명                |
     |     - 유효 기간                     |
     |                                    |
     |←-④ ServerHelloDone--------------|
     |                                    |
     |   의미: 서버 메시지 전송 완료        |
     |                                    |
     ↓                                    |
[인증서 검증 단계]                       |
     |                                    |
├─ CA 서명 확인                          |
│  (브라우저 내장 CA 리스트 확인)          |
├─ 유효 기간 확인                         |
├─ 도메인 일치 확인                       |
└─ 폐기 여부 확인 (OCSP/CRL)             |
     |                                    |
     |--⑤ ClientKeyExchange----------→|
     |                                    |
     |   전송 내용:                        |
     |   • Pre-Master Secret (48바이트)   |
     |     클라이언트가 생성한 랜덤 값      |
     |   • 서버 공개키(RSA)로 암호화됨     |
     |     "8f3e9d2a..." (암호화된 상태)   |
     |                                    |
     |                                    ↓
     |                          [개인키로 복호화]
     |                                    |
     |                          Pre-Master Secret 획득
     |                                    |
     ↓                                    ↓
[세션 키 생성]                    [세션 키 생성]
     |                                    |
재료:                                재료:
• 클라이언트 랜덤 "abc123..."        • 클라이언트 랜덤 "abc123..."
• 서버 랜덤 "xyz789..."              • 서버 랜덤 "xyz789..."
• Pre-Master Secret "pms456..."     • Pre-Master Secret "pms456..."
     |                                    |
     ↓                                    ↓
Master Secret 생성:                Master Secret 생성:
PRF(pms456..., "master secret",    PRF(pms456..., "master secret",
    abc123... + xyz789...)             abc123... + xyz789...)
     |                                    |
     ↓                                    ↓
"master789..."                      "master789..."
     |                                    |
     ↓                                    ↓
세션 키들 생성:                     세션 키들 생성:
PRF(master789..., "key expansion", PRF(master789..., "key expansion",
    xyz789... + abc123...)             xyz789... + abc123...)
     |                                    |
     ↓                                    ↓
• client_write_encryption_key       • client_write_encryption_key
• server_write_encryption_key       • server_write_encryption_key
• client_write_MAC_key              • client_write_MAC_key
• server_write_MAC_key              • server_write_MAC_key
     |                                    |
     |--⑥ ChangeCipherSpec-----------→|
     |                                    |
     |   의미: "이제부터 암호화 시작!"      |
     |                                    |
     |--⑦ Finished-------------------→|
     |                                    |
     |   내용:                             |
     |   • 지금까지 교환한 모든 메시지의    |
     |     해시값 (암호화됨)                |
     |   • 서버가 이걸 검증함               |
     |                                    |
     |                                    ↓
     |                          [Finished 메시지 검증]
     |                                    |
     |←-⑧ ChangeCipherSpec-------------|
     |                                    |
     |   의미: "OK, 암호화 시작!"          |
     |                                    |
     |←-⑨ Finished---------------------|
     |                                    |
     |   내용:                             |
     |   • 지금까지 교환한 모든 메시지의    |
     |     해시값 (암호화됨)                |
     |   • 클라이언트가 이걸 검증함         |
     |                                    |
     ↓                                    ↓
[Finished 메시지 검증]                    |
     |                                    |
     |←======암호화 통신 시작========→|
     |                                    |
     |   이제부터 모든 데이터는:            |
     |   • AES-256으로 암호화됨            |
     |   • MAC으로 무결성 검증됨           |
     |                                    |

각 단계 상세 설명

① ClientHello

클라이언트가 서버에게 "안녕, 연결하고 싶어요" 하며 자신의 능력을 알림

ClientHello 메시지 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

{
  "version": "TLS 1.2",
  "random": "abc123...", (32바이트)
  "session_id": "",
  "cipher_suites": [
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256",
    ...
  ],
  "compression_methods": ["null"],
  "extensions": {
    "server_name": "example.com",
    "supported_groups": ["x25519", "secp256r1"],
    "signature_algorithms": ["rsa_pss_rsae_sha256"]
  }
}

② ServerHello

서버가 "OK, 이 방식으로 하자" 하며 하나를 선택

ServerHello 메시지 구조:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

{
  "version": "TLS 1.2",
  "random": "xyz789...", (32바이트)
  "session_id": "session123...",
  "cipher_suite": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
  "compression_method": "null"
}

→ 클라이언트가 제안한 목록 중 하나를 선택

③ Certificate

서버가 "내가 진짜야" 하며 신분증(인증서) 제시

⑤ ClientKeyExchange

핵심! Pre-Master Secret을 안전하게 전달

Pre-Master Secret 생성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[클라이언트가 생성]
48바이트 랜덤 값
    ↓
0x03 0x03 (TLS 버전) + 46바이트 랜덤
    ↓
서버 공개키(RSA)로 암호화
    ↓
암호화된 Pre-Master Secret 전송

[서버가 수신]
    ↓
서버 개인키(RSA)로 복호화
    ↓
Pre-Master Secret 획득

→ 중간자는 개인키가 없어서 복호화 불가!

⑦, ⑨ Finished 메시지

지금까지의 모든 핸드셰이크 메시지가 변조되지 않았는지 최종 확인

Finished 메시지 검증:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

verify_data = PRF(
  master_secret,
  "client finished" 또는 "server finished",
  SHA256(모든 핸드셰이크 메시지)
)[12바이트]

→ 양쪽이 계산한 값이 일치하면 성공!
→ 중간자가 메시지를 변조했다면 불일치

인증서 검증 과정

클라이언트가 수행하는 4단계 검증

서버 인증서 검증:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[서버 인증서 수신]
    ↓

1단계: CA 서명 검증
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
브라우저에 내장된 신뢰할 수 있는
CA(인증 기관) 리스트 확인
    ↓
예시: DigiCert, Let's Encrypt, Comodo 등
    ↓
인증서의 발급자(Issuer) 확인
    ↓
해당 CA의 공개키로 인증서의 서명 검증
    ↓
서명이 유효한가?
    ↓
YES → ✓ CA가 정말 발급한 인증서
NO  → ✗ 위조된 인증서


2단계: 유효 기간 확인
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
인증서의 Not Before와 Not After 확인
    ↓
예시:
  Not Before: 2024-01-01 00:00:00 UTC
  Not After:  2025-12-31 23:59:59 UTC
    ↓
현재 시간이 이 범위 내에 있는가?
    ↓
YES → ✓ 유효 기간 내
NO  → ✗ 만료됨 또는 아직 유효하지 않음


3단계: 도메인 일치 확인
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
사용자가 접속하려는 도메인:
  https://example.com
    ↓
인증서의 Common Name (CN) 또는
Subject Alternative Name (SAN) 확인:
  CN: example.com
  SAN: example.com, www.example.com, *.example.com
    ↓
도메인이 일치하는가?
    ↓
YES → ✓ 올바른 서버의 인증서
NO  → ✗ 다른 서버의 인증서 (피싱 의심)


4단계: 폐기 여부 확인
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    ↓
인증서가 중간에 폐기되었는지 확인
    ↓
방법 1: CRL (Certificate Revocation List)
  • CA가 제공하는 폐기 목록 다운로드
  • 인증서 일련번호가 목록에 있는지 확인
  • 단점: 목록이 크고 느림
    ↓
방법 2: OCSP (Online Certificate Status Protocol)
  • CA에 실시간으로 질의
  • 응답: Good / Revoked / Unknown
  • 단점: CA 서버 부하, 프라이버시 문제
    ↓
방법 3: OCSP Stapling
  • 서버가 주기적으로 OCSP 응답 받아옴
  • 클라이언트에게 인증서와 함께 전송
  • 장점: 빠르고 프라이버시 보호
    ↓
폐기되지 않았는가?
    ↓
YES → ✓ 유효한 인증서
NO  → ✗ 폐기됨 (보안 사고 가능성)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

모든 검증 통과 시:
  ✓ 서버 인증 성공
  ✓ 공개키 신뢰 가능
  ✓ 핸드셰이크 계속 진행

하나라도 실패 시:
  ✗ 연결 중단
  ✗ 경고 메시지 표시

인증서 체인 검증

인증서 체인 (Certificate Chain):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[루트 CA]
DigiCert Global Root CA
  └─ 브라우저에 사전 설치됨
  └─ 자체 서명 (Self-signed)
     |
     | (서명)
     ↓
[중간 CA]
DigiCert SHA2 Secure Server CA
  └─ 루트 CA가 서명함
     |
     | (서명)
     ↓
[서버 인증서]
example.com
  └─ 중간 CA가 서명함

검증 과정:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. 서버 인증서의 서명을 중간 CA 공개키로 검증
2. 중간 CA 인증서의 서명을 루트 CA 공개키로 검증
3. 루트 CA는 브라우저에 내장되어 신뢰됨

→ 체인이 끊기지 않고 루트 CA까지 연결되면 신뢰!

하이브리드 암호화 시스템

왜 두 가지 암호화를 함께 사용하나?

암호화 방식별 장단점:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[공개키 암호화 (RSA, ECC)]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

장점:
  ✓ 키 교환 안전
  ✓ 사전 키 공유 불필요
  ✓ 전자 서명 가능

단점:
  ✗ 속도 매우 느림 (1000배 이상)
  ✗ 복잡한 수학 연산
  ✗ CPU 부하 높음

성능:
  RSA-2048로 1MB 암호화: ~10초

용도:
  → 세션 키 교환에만 사용


[대칭키 암호화 (AES)]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

장점:
  ✓ 속도 매우 빠름
  ✓ 간단한 연산
  ✓ CPU 부하 낮음
  ✓ 하드웨어 가속 지원

단점:
  ✗ 키 교환 어려움
  ✗ 사전 키 공유 필요
  ✗ 전자 서명 불가

성능:
  AES-256으로 1MB 암호화: ~0.01초

용도:
  → 실제 데이터 암호화에 사용

TLS의 하이브리드 방식

하이브리드 암호화 흐름:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Phase 1: 핸드셰이크 (공개키 암호화)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

목적: 세션 키를 안전하게 교환

[클라이언트]
    ↓
1. Pre-Master Secret 생성
   48바이트 랜덤 값: "pms456..."
    ↓
2. 서버 공개키(RSA)로 암호화
   RSA-2048 암호화
   걸리는 시간: ~0.01초
    ↓
3. 암호화된 값 전송
   "8f3e9d2a..." (256바이트)
    ↓

[서버]
    ↓
1. 서버 개인키(RSA)로 복호화
   RSA-2048 복호화
   걸리는 시간: ~0.1초
    ↓
2. Pre-Master Secret 획득
   "pms456..."

→ 양쪽 모두 Pre-Master Secret 보유
→ 같은 알고리즘으로 세션 키(AES) 생성

특징:
• 느리지만 안전한 키 교환
• 단 한 번만 수행 (핸드셰이크 시)
• 이후에는 사용 안 함


Phase 2: 데이터 전송 (대칭키 암호화)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

목적: 빠른 속도로 대용량 데이터 암호화

[클라이언트] → [서버]
    ↓
HTTP 요청:
"GET /api/data HTTP/1.1
Host: example.com
..."
    ↓
AES-256-GCM으로 암호화
   걸리는 시간: ~0.0001초
    ↓
암호화된 데이터 전송

[서버] → [클라이언트]
    ↓
JSON 응답:
"{data: [...]}" (1MB)
    ↓
AES-256-GCM으로 암호화
   걸리는 시간: ~0.01초
    ↓
암호화된 데이터 전송

특징:
• 매우 빠른 암호화/복호화
• 모든 애플리케이션 데이터에 사용
• 세션 동안 지속적으로 사용

실제 HTTPS 통신 예시

실제 웹 페이지 로딩:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[1] TCP 3-way handshake
    시간: ~50ms
    ↓

[2] TLS 핸드셰이크 (RSA 사용)
    • ClientHello
    • ServerHello + Certificate
    • ClientKeyExchange (RSA 암호화)
    • ChangeCipherSpec + Finished
    시간: ~200ms
    ↓

[3] HTTP 요청 (AES 암호화)
    GET /index.html HTTP/1.1
    크기: 500 bytes
    암호화 시간: 0.0001ms
    전송 시간: ~10ms
    ↓

[4] HTTP 응답 (AES 암호화)
    HTML + CSS + JS
    크기: 500KB
    암호화 시간: 5ms
    전송 시간: ~500ms
    ↓

[5] 추가 리소스 요청 (AES 암호화)
    이미지 10개 (각 100KB)
    각 암호화 시간: 1ms
    전송 시간: ~1000ms

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

총 소요 시간:
• 핸드셰이크 (RSA): 200ms (한 번만)
• 데이터 전송 (AES): 1510ms (지속적)

만약 RSA만 사용했다면:
• 핸드셰이크: 200ms
• 데이터 전송: 150,000ms (100배 느림!)

→ 하이브리드 방식으로 150초 → 1.7초로 단축!

세션 키 생성 메커니즘

양쪽이 같은 키를 만드는 방법

세션 키 생성 재료:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[클라이언트]                    [서버]
     |                            |
재료 1: 클라이언트 랜덤         재료 1: 클라이언트 랜덤
"abc123..." (32바이트)         "abc123..." (받음)
     |                            |
재료 2: 서버 랜덤                재료 2: 서버 랜덤
"xyz789..." (받음)              "xyz789..." (32바이트)
     |                            |
재료 3: Pre-Master Secret       재료 3: Pre-Master Secret
"pms456..." (생성)              "pms456..." (복호화로 획득)
     |                            |
     └────────────┬───────────────┘
                  ↓
          양쪽 모두 같은 재료 보유!

Master Secret 생성 (1단계)

Master Secret 생성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

입력:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• secret: pre_master_secret "pms456..."
• label:  "master secret" (ASCII 문자열)
• seed:   client_random + server_random
          "abc123..." + "xyz789..."

처리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
master_secret = PRF(
    secret: "pms456...",
    label:  "master secret",
    seed:   "abc123..." + "xyz789..."
)

PRF 내부 동작:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

A(0) = seed = "abc123xyz789..."

A(1) = HMAC-SHA256(secret, A(0))
     = HMAC-SHA256("pms456...", "abc123xyz789...")
     = "hash1..."

P(1) = HMAC-SHA256(secret, A(1) + label + seed)
     = HMAC-SHA256("pms456...",
                   "hash1..." + "master secret" + "abc123xyz789...")
     = "result1..."

A(2) = HMAC-SHA256(secret, A(1))
     = "hash2..."

P(2) = HMAC-SHA256(secret, A(2) + label + seed)
     = "result2..."

master_secret = P(1) + P(2) [처음 48바이트]

출력:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
master_secret = "ms789abc..." (48바이트)

→ 클라이언트와 서버가 동일한 값 생성!

Key Block 생성 (2단계)

Key Block 생성 (여러 키들):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

입력:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
• secret: master_secret "ms789abc..."
• label:  "key expansion" (ASCII 문자열)
• seed:   server_random + client_random (순서 주의!)
          "xyz789..." + "abc123..."

처리:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
key_block = PRF(
    secret: "ms789abc...",
    label:  "key expansion",
    seed:   "xyz789..." + "abc123..."  ← 순서 바뀜!
)

출력:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
key_block = "kb123def456ghi789jkl..." (충분히 긴 바이트 배열)


Key Block 분배:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

key_block을 정해진 순서대로 자르기:

바이트 [0-31]:    client_write_MAC_key
                 (클라이언트→서버 무결성 검증용)
                 "mac_c_123..."

바이트 [32-63]:   server_write_MAC_key
                 (서버→클라이언트 무결성 검증용)
                 "mac_s_456..."

바이트 [64-95]:   client_write_encryption_key
                 (클라이언트→서버 암호화용)
                 "enc_c_789..."

바이트 [96-127]:  server_write_encryption_key
                 (서버→클라이언트 암호화용)
                 "enc_s_abc..."

바이트 [128-143]: client_write_IV
                 (초기화 벡터)
                 "iv_c_def..."

바이트 [144-159]: server_write_IV
                 (초기화 벡터)
                 "iv_s_ghi..."

→ 양쪽이 완전히 동일한 키들을 얻음!

왜 이렇게 복잡한가?

보안을 위한 설계:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[랜덤성 증가]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

client_random + server_random 결합
    ↓
양쪽 모두 랜덤 값 제공
    ↓
어느 한쪽도 결과를 예측할 수 없음
    ↓
재료: "abc123..." + "xyz789..." = "abc123xyz789..."
    ↓
256비트 엔트로피


[다른 용도의 키 구분]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

같은 재료로 다른 키를 만들 때:

Master Secret 생성:
PRF(pms, "master secret", c_rand + s_rand)

Key Expansion:
PRF(ms, "key expansion", s_rand + c_rand)

→ 레이블과 순서를 바꿔서 구분!
→ 충돌 방지


[순서 바꾸기]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Master Secret:
  client_random + server_random
  "abc123xyz789..."

Key Block:
  server_random + client_random
  "xyz789abc123..."

→ 추가 엔트로피 확보
→ 패턴 공격 방어


[Perfect Forward Secrecy]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

각 세션마다:
  • 새로운 client_random
  • 새로운 server_random
  • 새로운 pre_master_secret
    ↓
  매번 다른 세션 키 생성!
    ↓
  과거 세션 기록이 노출되어도
  다른 세션은 안전

PRF와 레이블의 역할

PRF란?

PRF = Pseudo-Random Function:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

의사 난수 함수
• 결정적(Deterministic): 같은 입력 → 같은 출력
• 예측 불가능: 입력의 일부만 바뀌어도 완전히 다른 출력
• 일방향: 출력에서 입력을 역산 불가능

TLS 1.2의 PRF:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

HMAC-SHA256 기반

레이블의 역할

레이블은 TLS RFC 표준에 정의된 고정 문자열

레이블(Label) = 구분용 상수:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

같은 재료로 다른 결과를 만들기 위한 장치

비유:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

재료: 밀가루 + 물 + 소금

"빵" + 재료 → 빵 반죽
"면" + 재료 → 면 반죽
"만두피" + 재료 → 만두피 반죽

→ 같은 재료지만 레이블에 따라 다른 결과!

TLS에서 사용되는 레이블들

TLS 1.2 RFC 5246에 정의된 레이블:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

"master secret"
  → Master Secret 생성
  → PRF(pre_master_secret, "master secret", randoms)

"key expansion"
  → 세션 키들 생성
  → PRF(master_secret, "key expansion", randoms)

"client finished"
  → 클라이언트 Finished 메시지 검증
  → PRF(master_secret, "client finished", handshake_hash)

"server finished"
  → 서버 Finished 메시지 검증
  → PRF(master_secret, "server finished", handshake_hash)

→ 모두 RFC 문서에 정확히 명시됨
→ 전 세계 모든 TLS 구현체가 동일하게 사용

왜 레이블을 사용하나?

레이블이 필요한 이유:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

만약 레이블이 없다면?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PRF(pre_master_secret, client_random + server_random)
  → 결과 A

PRF(master_secret, server_random + client_random)
  → 결과 B

문제:
• 순서만 바뀌어서 혼동 가능
• 같은 값이 다른 용도로 사용될 위험
• 보안 취약점 발생 가능


레이블을 사용하면?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PRF(pre_master_secret,
    "master secret" + client_random + server_random)
  → 결과 A

PRF(master_secret,
    "key expansion" + server_random + client_random)
  → 결과 B

장점:
✓ 서로 다른 용도임을 명확히 구분
✓ 같은 입력으로 다른 키 생성 가능
✓ 충돌 방지 (키가 우연히 같아지는 것 방지)
✓ 보안 강화
✓ 표준화 (모든 구현체가 동일하게 동작)

실제 계산 예시

구체적인 예시 (단순화):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

재료:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
pre_master_secret = 0x1234ABCD...
client_random = 0xAAAA...
server_random = 0xBBBB...


1단계: Master Secret 만들기
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

label = "master secret"
ASCII 인코딩:
  0x6D 0x61 0x73 0x74 0x65 0x72 0x20
  0x73 0x65 0x63 0x72 0x65 0x74

seed = client_random + server_random
     = 0xAAAA... + 0xBBBB...

HMAC 입력 = label + seed
          = "master secret" + 0xAAAABBBB...

master_secret = PRF(0x1234ABCD..., HMAC 입력)
              = 0x5678EFGH... (48바이트)


2단계: Key Block 만들기
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

label = "key expansion"
ASCII 인코딩:
  0x6B 0x65 0x79 0x20 0x65 0x78 0x70
  0x61 0x6E 0x73 0x69 0x6F 0x6E

seed = server_random + client_random (순서 바뀜!)
     = 0xBBBB... + 0xAAAA...

HMAC 입력 = label + seed
          = "key expansion" + 0xBBBBAAAA...

key_block = PRF(0x5678EFGH..., HMAC 입력)
          = 0x9ABC1234... (필요한 만큼)

→ 이제 key_block을 잘라서 각 키로 사용!

MAC을 통한 무결성 보호

MAC이란?

MAC = Message Authentication Code:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

메시지의 "지문(fingerprint)" 또는 "체크섬"
• 메시지가 변조되었는지 확인
• 키를 사용하므로 위조 불가능
• 암호화와는 별개 (암호화 = 기밀성, MAC = 무결성)

TLS 1.2에서:
  HMAC-SHA256 사용 (32바이트)

송신 과정

메시지 송신 시 MAC 생성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[클라이언트가 "Hello World"를 서버에 보낼 때]

1단계: MAC 계산
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

원본 메시지: "Hello World"
    ↓
MAC 계산:
    ↓
MAC = HMAC-SHA256(
    key:  client_write_MAC_key,    ← 세션 키
    data: sequence_number +         ← 패킷 순서
          record_type +             ← 레코드 타입
          TLS_version +             ← TLS 버전
          length +                  ← 길이
          "Hello World"             ← 실제 메시지
)

구체적 예:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
key:  "mac_c_123..." (32바이트)
data:
  0x0000000000000001 (sequence = 1)
  0x17 (application data)
  0x0303 (TLS 1.2)
  0x000B (length = 11)
  "Hello World"

MAC 결과: "8f3e9d2a..." (32바이트)


2단계: 메시지 + MAC 결합
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─────────────────────────┐
│ 평문 메시지:             │
│ "Hello World"           │
├─────────────────────────┤
│ MAC:                    │
│ "8f3e9d2a..." (32바이트)│
└─────────────────────────┘


3단계: 전체 암호화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

AES-256-CBC로 암호화
(client_write_encryption_key 사용)
    ↓
┌─────────────────────────┐
│ 🔒 암호화된 데이터       │
│ [메시지 + MAC 모두 암호화]│
│ "A7F3B2C9..."           │
└─────────────────────────┘
    ↓
네트워크로 전송

수신 과정

메시지 수신 시 MAC 검증:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[서버가 패킷을 받았을 때]

1단계: 복호화
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─────────────────────────┐
│ 🔒 암호화된 데이터       │
│ "A7F3B2C9..."           │
└─────────────────────────┘
    ↓
AES-256-CBC 복호화
(client_write_encryption_key 사용)
    ↓
┌─────────────────────────┐
│ 메시지:                  │
│ "Hello World"           │
├─────────────────────────┤
│ 받은 MAC:               │
│ "8f3e9d2a..."           │
└─────────────────────────┘


2단계: MAC 재계산
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

받은 메시지로 MAC을 다시 계산:
    ↓
계산된 MAC = HMAC-SHA256(
    key:  client_write_MAC_key,    ← 같은 키!
    data: 0x0000000000000001 +     ← 같은 sequence
          0x17 + 0x0303 + 0x000B +
          "Hello World"             ← 받은 메시지
)
    ↓
계산된 MAC: "8f3e9d2a..."


3단계: MAC 비교
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

받은 MAC:    "8f3e9d2a..."
계산된 MAC:  "8f3e9d2a..."
    ↓
바이트 단위 비교
    ↓
일치하는가?
    ↓
YES → ✓ 무결성 확인
      ✓ 메시지 사용
      ✓ sequence 증가

NO  → ✗ 변조 감지!
      ✗ 연결 즉시 종료
      ✗ fatal alert 전송

변조 공격 시나리오

공격자가 변조 시도:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

시나리오 1: 암호화된 데이터 무작위 변조
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[클라이언트 송신]
암호화: "A7F3B2C9..."
    ↓
[공격자가 중간에서 변조]
변조: "A7F39999..." (일부 비트 변경)
    ↓
[서버 수신]
    ↓
복호화 결과: "H3ll0 W@rld" (깨진 데이터)
받은 MAC: "8f3e9d2a..."
    ↓
서버가 MAC 재계산:
HMAC-SHA256(key, "H3ll0 W@rld")
    ↓
계산된 MAC: "2b7f5a1c..." ← 다름!
    ↓
비교: "8f3e9d2a..." ≠ "2b7f5a1c..."
    ↓
✗ 불일치! 변조 감지!
    ↓
[연결 즉시 종료]


시나리오 2: 복호화 후 메시지 변조
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

가정: 공격자가 암호화를 풀 수 있다면?

[공격자가 메시지 변경]
메시지: "Hello World" → "Hello Hacker"
MAC: "8f3e9d2a..." (원본 그대로)
    ↓
[서버가 검증]
받은 메시지: "Hello Hacker"
받은 MAC: "8f3e9d2a..."
    ↓
MAC 재계산:
HMAC-SHA256(key, "Hello Hacker")
    ↓
계산된 MAC: "9a4f2e8b..." ← 다름!
    ↓
비교: "8f3e9d2a..." ≠ "9a4f2e8b..."
    ↓
✗ 불일치! 변조 감지!


시나리오 3: 메시지 + MAC 둘 다 변조
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

공격자가 시도:
메시지: "Hello World" → "Hello Hacker"
    ↓
새로운 MAC 생성 시도:
HMAC-SHA256(???, "Hello Hacker")
    ↓
문제: MAC 키를 모름!
    ↓
client_write_MAC_key는:
  • 핸드셰이크에서 안전하게 교환됨
  • 공개키로 암호화되어 전송됨
  • 공격자는 이 키를 알 수 없음
    ↓
✗ 올바른 MAC 생성 불가능!
    ↓
[서버가 여전히 탐지함]


시나리오 4: 재전송 공격 (Replay Attack)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

정상 통신:
패킷 1: "Transfer $100" + MAC(seq=1, ...)
패킷 2: "Check balance" + MAC(seq=2, ...)

공격자가 패킷 1을 재전송:
패킷 1 재전송: "Transfer $100" + MAC(seq=1, ...)
    ↓
[서버 검증]
현재 sequence: 2
받은 sequence: 1
    ↓
"어? seq=1은 이미 처리했는데?"
    ↓
✗ 재전송 공격 탐지!
    ↓
[패킷 폐기]

MAC에 포함되는 추가 정보

MAC 계산 시 포함 데이터:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

MAC = HMAC-SHA256(MAC_key, data)

data 구성:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌────────────────────────┐
│ sequence_number        │  ← 8바이트, 재전송 공격 방지
│ 0x0000000000000001     │
├────────────────────────┤
│ record_type            │  ← 1바이트
│ 0x17 (application)     │
├────────────────────────┤
│ TLS_version            │  ← 2바이트
│ 0x0303 (TLS 1.2)       │
├────────────────────────┤
│ length                 │  ← 2바이트
│ 0x000B (11 bytes)      │
├────────────────────────┤
│ message_content        │  ← 실제 메시지
│ "Hello World"          │
└────────────────────────┘

→ 이 모든 것을 합쳐서 MAC 계산!
→ 어느 하나라도 바뀌면 MAC 완전히 달라짐

TLS 1.3의 개선 (AEAD)

TLS 1.3: AEAD 통합 방식:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

TLS 1.2 (MAC-then-Encrypt):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[메시지] → [MAC 추가] → [전체 암호화] → [전송]

단점:
• 두 단계 필요 (MAC + 암호화)
• 패딩 오라클 공격에 취약 가능


TLS 1.3 (AEAD):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[메시지] → [암호화 + 인증 동시에] → [전송]

AEAD = Authenticated Encryption with Associated Data

알고리즘:
• AES-GCM (Galois/Counter Mode)
• ChaCha20-Poly1305

장점:
✓ 한 번의 연산으로 암호화 + MAC
✓ 더 빠름
✓ 더 안전 (패딩 오라클 공격 불가)
✓ 별도의 MAC 키 불필요

AES-GCM 예시:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

입력:
• 키: encryption_key
• 논스(nonce): 12바이트
• 평문: "Hello World"
• 추가 인증 데이터(AAD): 헤더 정보

출력:
• 암호문: "A7F3B2C9..."
• 인증 태그: "8f3e9d2a..." (16바이트)

→ 하나의 연산으로 암호화 + MAC!